//------------------------------------------------------------------------------
// X2D Game Engine
// Copyright (C) 2006, 2007, 2008 Janusz Kowalski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

#include <cassert>

#ifdef X2D_WITH_OPENGL
#ifdef OS_WINDOWS
#include <windows.h>
#endif
#include "GL/gl.h"
#include "GL/glu.h"
#endif

#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

#include "Box2D.h"

#include "pmsg.h"

#include "x2d_New.h"
#include "global.h"
#include "x2d_ext.h"

#define X2D_PPM 64

namespace x2d {


// Returns the point of the dst where this function finished.
static char *strCopy(char *dst, const char *src) {
  int i;
  for(i=0; src[i]; i++)
    dst[i] = src[i];
  return dst+i;
}

// inline int max(int a, int b) { return (a>b?a:b); }

// inline int min(int a, int b) { return (a<b?a:b); }

template <class T1, class T2> inline bool sameSign(T1 a, T2 b) {
  if(((a>=0) && (b>=0)) || ((a<0) && (b<0))) return true;
  else return false;
}

// Returns: true - a and b have different signs; false - otherwise.
template <class T1, class T2> inline bool diffSign(T1 a, T2 b) {
  if(((a>0) && (b<0)) || ((a<0) && (b>0))) return true;
  else return false;
}

const int NFORMS = 60;

static const char* formId2Name[NFORMS] = {
//0123456789ABCDEFGHI0123456789ABCDEFGHI0123456789ABCDEFGHI0123456789ABCDEFGHI
  "Empty",           "MapGrid",         "brick7",          "brick8",
  "BigTree1",        "BigTree2",        "BigTree3",        "BigTree4",
  "BigTree5",        "BigTree6",        "FunnyPlant",      "ShyFunnyPlant",
  "Liana1",          "Liana2",          "Liana3",          "Soil",
  "SoilSurf",        "SadStone",        "EMPlayerStart",   "bLabWallB",
  "bLabScreen",      "bLabBox",         "truss",           "tSteel0",
  "tSteel1",         "tSteel2",         "tSteel3",         "tSteel4",
  "tSteel5",         "tSteel6",         "tSteel7",         "tSteel8",
  "RBG_Wall",        "RBG_HTank",       "MarWall1",        "MarWall2",
  "MarWall3",        "MarWall4",        "MarWall5",        "MarWall6",
  "MarWall7",        "MarWall8",        "MarWall9",        "MarWallA",
  "MarWallB",        "MarWallC",        "MarWallD",        "RActiveBarrelB",
  "RActiveBarrelT",  "BHazardBarrelB",  "BHazardBarrelT",  "SWall",
  "ELWXX",           "ELW00",           "ELW10",           "ELW20",
  "ELW02",           "ELW12",           "ELW22",           "#PolyEnt"
};

int formId2Plane[NFORMS] = {
  PLANE_RESERVED,     PLANE_RESERVED,     PLANE_ACTION,       PLANE_ACTION,
  PLANE_ACTION,       PLANE_ACTION,       PLANE_ACTION,       PLANE_ACTION,
  PLANE_ACTION,       PLANE_ACTION,       PLANE_FOREGROUND,   PLANE_BACKGROUND,
  PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_ACTION,
  PLANE_ACTION,       PLANE_FOREGROUND,   PLANE_MAGIC,        PLANE_BACKGROUND,
  PLANE_ACTION,       PLANE_ACTION,       PLANE_ACTION,       PLANE_FOREGROUND,
  PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,
  PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,
  PLANE_BACKGROUND,   PLANE_BACKGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,
  PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,
  PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,
  PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,
  PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_FOREGROUND,   PLANE_BACKGROUND,
  PLANE_BACKGROUND,   PLANE_BACKGROUND,   PLANE_BACKGROUND,   PLANE_BACKGROUND,
  PLANE_BACKGROUND,   PLANE_BACKGROUND,   PLANE_BACKGROUND,   PLANE_ACTION
};

EntityHandler::EntityHandler() { }

EntityHandler::~EntityHandler() { }

TestEntityHandler::TestEntityHandler(World *world, unsigned formId) :
  EntityHandler(), world_(world)
{
  fs::path path;

  // Try to open .png file.
  // !! No converstion string -> char* should be performed.
  path = fs::path(X2D_PATH_TILESET) / (std::string(formId2Name[formId]) + ".png");
  if(fs::exists(path)) {
    anim_=new NoAnim(path.string().c_str());
    return;
  }
  
  // Try to open .ani file.
  // !! No converstion string -> char* should be performed.
  path = fs::path(X2D_PATH_TILESET) / (std::string(formId2Name[formId]) + ".ani");
  if(fs::exists(path)) {
    anim_=new FrameAnim(path.string().c_str());
    return;
  }
  
  anim_=0;
  pmsgErrorF("Data for \"%s\" tile cannot be found [%s]!\n", formId2Name[formId], path.file_string().c_str());
}

void TestEntityHandler::draw(GraphicContext &g, float32 x, float32 y, b2Body *body) {
  if(!!anim_) {
    Image &img = anim_->getImage();
    g.drawImage(img,x-img.getW()/2,y-img.getH()/2);
  }
}

EntityHandler *TestEntityHandler::createEntity(int x, int y) {
  b2World *b2world = world_->getB2World();
    
    // Shape
  b2PolygonDef shapeDef;
  shapeDef.SetAsBox(0.25f, 0.25f);
    
    // New static body
  b2BodyDef bodyDef;
  bodyDef.userData=this;
  bodyDef.position.Set(x/float32(X2D_PPM), y/float32(X2D_PPM));
  b2Body *body = b2world->CreateStaticBody(&bodyDef);
  body->CreateShape(&shapeDef);

  // !! ??
  return this;
}

PolyEntityHandler::PolyEntityHandler(World *world) : EntityHandler(), world_(world) {
}

// !! Get rid of this.
#define UGLY_PI 3.14159

void PolyEntityHandler::draw(GraphicContext &g, float32 x, float32 y, b2Body *body) {
  g.startOpenGL();
  //glBlendFunc (GL_SRC_ALPHA_SATURATE, GL_ONE);
      glDisable(GL_TEXTURE_2D);
      //glClear (GL_COLOR_BUFFER_BIT);


      glEnable (GL_BLEND);
      glEnable (GL_POLYGON_SMOOTH);
      glDisable (GL_DEPTH_TEST);
      glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
      glColor4f(0.0f,0.0f,0.0f,1.0f);

  glTranslatef(x,y,0);
  glRotatef(static_cast<GLfloat>(body->GetAngle()*180/UGLY_PI),0,0,1);
  // Iterate over the shapes of the body.
  b2Shape *shape = body->GetShapeList();
  while(shape) {
    b2PolygonShape *poly = dynamic_cast<b2PolygonShape*>(shape);
    ASSURE(poly!=0);
    unsigned n = poly->GetVertexCount();
    glBegin(GL_POLYGON);
    for(unsigned i=0; i<n; i++) {
      const b2Vec2 &xy = poly->GetVertices()[i];
      glVertex2f(xy.x*64, xy.y*64);
    }
    glEnd();
    shape=shape->GetNext();
  }

  g.endOpenGL();
}

EntityHandler *PolyEntityHandler::createEntity(int x, int y) {
  // !! Get rid of this.
  
  b2World *b2world = world_->getB2World();
    
    // Shape
  b2PolygonDef shapeDef;
  shapeDef.SetAsBox(0.25f, 0.25f);
    
    // New static body
  b2BodyDef bodyDef;
  bodyDef.userData=this;
  bodyDef.position.Set(x/float32(X2D_PPM), y/float32(X2D_PPM));
  b2Body *body = b2world->CreateStaticBody(&bodyDef);
  body->CreateShape(&shapeDef);

  //!! ??
  return this;
}

FormList::FormList(World *world) {
  pmsgInfoF("FormList\n");

  for(int formId=0; formId<NFORMS; formId++) {
    if(formId2Name[formId][0]=='#') {
      if(strcmp(formId2Name[formId]+1,"PolyEnt")==0) {
        entityHandlerPtrList_.add(new PolyEntityHandler(world));
      } else ASSURE(false);
    } else {
      entityHandlerPtrList_.add(new TestEntityHandler(world,formId));
    }
  }
}

FormList::~FormList() {
  for(unsigned i=entityHandlerPtrList_.size(); i--;) {
    delete entityHandlerPtrList_[i];
  }
}

void FormList::createForm(World *world, unsigned formId, int x, int y) {
  entityHandlerPtrList_[formId]->createEntity(x,y);
}

int FormList::getPlane(int formId) {
  assert(formId<NFORMS && formId>=0);
  return formId2Plane[formId];
}

int FormList::getNextFormId(int formId, int plane) {
  return (formId+1<NFORMS?formId+1:0);
}

int FormList::getPreviousFormId(int formId, int plane) {
  return (formId>0?formId-1:NFORMS-1);
}

EntityHandler* FormList::getEntityHandler(unsigned formId) {
  return entityHandlerPtrList_[formId];
}

Uint32 FormList::getIdOfEntityHandler(EntityHandler *handler) {
  Uint32 id = entityHandlerPtrList_.indexOf__(handler);
  ASSURE(id<entityHandlerPtrList_.size());
  return id;
}

EntityHandler *FormList::getEntityHandlerById(Uint32 id) {
  return entityHandlerPtrList_[id];
}

//-----------------------------------------------------------------------------
// Entity class
//-----------------------------------------------------------------------------

// Add itself to the world
Entity::Entity(World *world) {
  world_ = world;
  world_->add(this);
}

Entity::~Entity() {
  world_->remove(this);
}

void Entity::draw(GraphicContext &g, float x, float y) {
  //g.blitImage(getLook(),x,y);
}

bool Entity::collision(int formId) {
  return true;
}

void Entity::timeCb() {
}

// Map constant.
#define X2D_PPM 64

//-----------------------------------------------------------------------------
// Pysical entity
//-----------------------------------------------------------------------------

PhysicalEntity::PhysicalEntity(World *world, float32 x, float32 y) : Entity(world) {
  b2World *w = world->getB2World();
  b2BodyDef bodyDef;
  bodyDef.position.Set(x/X2D_PPM,y/X2D_PPM);
  bodyDef.userData=0;
  body_=w->CreateDynamicBody(&bodyDef);
}

PhysicalEntity::~PhysicalEntity() {
  world_->getB2World()->DestroyBody(body_);
}

double PhysicalEntity::getX() {
  b2Vec2 xy = body_->GetPosition();
  return xy.x * X2D_PPM;
}

double PhysicalEntity::getY() {
  b2Vec2 xy = body_->GetPosition();
  return xy.y * X2D_PPM;
}

void PhysicalEntity::timeCb() {
}

// Entity may have simple graphical representation, that we'd like to rotate
// around its center. However entity may be something more too.
void PhysicalEntity::draw(GraphicContext &g, float x, float y) {
}

void PhysicalEntity::contact(b2Body &body, b2Shape &shape1, b2Shape &shape2) { }

//-----------------------------------------------------------------------------
// Physical circle
//-----------------------------------------------------------------------------

PhyCircleEnt::PhyCircleEnt(World *world, float32 x, float32 y, float32 r)
: PhysicalEntity(world, x, y), r_(r), img_(X2D_PATH_DATA "barr.png") {
  b2CircleDef shapeDef;
  shapeDef.radius=r/X2D_PPM;
  shapeDef.density=1.0f;
  shapeDef.friction=0.3f;
  shapeDef.restitution=0.5f;
  body_->CreateShape(&shapeDef);
  body_->SetMassFromShapes();
}

void PhyCircleEnt::draw(GraphicContext &g, float x, float y) {
  float angle = body_->GetAngle();
  g.drawTexturedCircle(img_,x,y,r_,angle);
  //g.drawCircle(x+cos(angle)*16,y+sin(angle)*16,16);
}

void PhyCircleEnt::contact(b2Body &body, b2Shape &shape1, b2Shape &shape2) { }

//-----------------------------------------------------------------------------
// Physical test entity
//-----------------------------------------------------------------------------

PhyEnt::PhyEnt(World *world) : Entity(world), img_(X2D_PATH_ENTITIES "sbwl.png") {
  b2BodyDef bodyDef;
  // !! Should be changed.
  bodyDef.position.Set(static_cast<float32>(rand()%512/32.0/2.0), static_cast<float32>((128+rand()%128)/32.0/2.0));
  b2World *b2w = world->getB2World();
  body_ = b2w->CreateDynamicBody(&bodyDef);
  b2PolygonDef shapeDef;
  shapeDef.SetAsBox(0.25f, 0.25f, b2Vec2(0.25f, 0.25f), 0);
  shapeDef.density=1.0f;
  shapeDef.friction=0.3f;
  body_->CreateShape(&shapeDef);
  body_->SetMassFromShapes();
  right_=true;
}

PhyEnt::~PhyEnt() {
  b2World *b2w = world_->getB2World();
  b2w->DestroyBody(body_);
}

void PhyEnt::timeCb() {
   if(rand()%500==0) right_=!right_;
 
   // Don't move if there's vertical velocity component.
   b2Vec2 v = body_->GetLinearVelocity();
   if(v.y>0) return;
   
   // Don't move if the velocity is too big.
   if(v.Length()>1) return;
   
   // Don't move if the angle is wrong.
   float a = body_->GetAngle();
   b2Vec2 point = body_->GetPosition();
  
   if(cos(a)>0.5) {
     b2Vec2 force(3,0); if(!right_) force*=-1;
     body_->ApplyForce(force, point+b2Vec2(0.25,0.25));
   } else {
     if(rand()%100) return;
     b2Vec2 imp(0,-0.7f);
     body_->ApplyImpulse(imp,point);
   }
}



double PhyEnt::getX() {
  b2Vec2 xy = body_->GetPosition();
  return xy.x * X2D_PPM;
}

double PhyEnt::getY() {
  b2Vec2 xy = body_->GetPosition();
  return xy.y * X2D_PPM;
}

Image& PhyEnt::getLook() {
  return img_;
}

void PhyEnt::draw(GraphicContext &g, int x, int y) {
  float angle = body_->GetAngle();
  g.TMPblitImage(img_, x, y, angle);
}

//-----------------------------------------------------------------------------
// Player Entity
//-----------------------------------------------------------------------------

PlayerEnt::PlayerEnt(World *world) : Entity(world), img_(X2D_PATH_ENTITIES "robotR.png") {
  goLeft_ = false;
  goRight_ = false;
  jump_ = false;
  xVelocity_=0; yVelocity_=0;
  setX(256);
  setY(256);
  onTheGround_ = false;
}

PlayerEnt::~PlayerEnt() { }

Image &PlayerEnt::getLook() {
  return img_;
}

static const float PLAYER_X_ACCEL = 0.1f;

// Gravitational acceleration [pixels per 10 miliseconds]
static const float GRAV_ACCEL = 0.048f;

void PlayerEnt::timeCb() {

  if(goLeft_ == goRight_) {
    if(xVelocity_>PLAYER_X_ACCEL) {
      xVelocity_-=PLAYER_X_ACCEL;
    } else if(xVelocity_<-PLAYER_X_ACCEL) {
      xVelocity_+=PLAYER_X_ACCEL;
    } else xVelocity_=0;
  } else {
    if(goRight_) {
      xVelocity_+=PLAYER_X_ACCEL; if(xVelocity_>4) xVelocity_=4;
    } else {  // goLeft_ == true
      xVelocity_-=PLAYER_X_ACCEL; if(xVelocity_<-4) xVelocity_=-4;
    }
  }
  
  //if(jump_ && onTheGround_) {
  //  yVelocity_ = -4;
  //}
  
  // Air resistance or something...
  //if(!jump_ && yVelocity_<0) yVelocity_+=0.1f;
  
  // Apply gravity
  //if(yVelocity_ + GRAV_ACCEL<4) yVelocity_+=GRAV_ACCEL;
  onTheGround_ = false;
  
  // Set the new position.
  setX(getX()+xVelocity_);
  setY(getY()+yVelocity_);
  
  // Get map
  Map &map = *world_->getMap();
}

//-----------------------------------------------------------------------------
// Something bad entity.
//-----------------------------------------------------------------------------
SomethingBadEnt::SomethingBadEnt(World *world) : Entity(world) {
  sbLeft_ = new NoAnim(X2D_PATH_ENTITIES "sbwl.png");
  sbRight_ = new NoAnim(X2D_PATH_ENTITIES "sbwr.png");
  
  xVelocity_ = -1;
  lookLeft_ = true;
  yVelocity_ = 0;
  
  setX(32);
  setY(32);
}

SomethingBadEnt::~SomethingBadEnt() {
  delete sbLeft_;
  delete sbRight_;
}

Image &SomethingBadEnt::getLook() {
  if(lookLeft_) return sbLeft_->getImage();
  else return sbRight_->getImage();
}

void SomethingBadEnt::timeCb() {
  // Apply gravity
  //if(yVelocity_ + GRAV_ACCEL<4) yVelocity_+=GRAV_ACCEL;
  
  setX(getX()+xVelocity_);
  setY(getY()+yVelocity_);
}

//-----------------------------------------------------------------------------
// World's contact listener
//-----------------------------------------------------------------------------

ContactListener::ContactListener(World *world) : b2ContactListener(), world_(world) { }

void ContactListener::Add(b2ContactPoint *point) {
  b2Body *body1 = point->shape1->GetBody();
  b2Body *body2 = point->shape2->GetBody();

  if(body1->GetUserData()!=0) {
    // !! Make it working again.
    //static_cast<PhysicalEntity*>(body1->GetUserData())->contact(*body2,*point->shape1,*point->shape2);
  }

  if(body2->GetUserData()!=0) {
    // !! Make it working again.
    //static_cast<PhysicalEntity*>(body2->GetUserData())->contact(*body1,*point->shape2,*point->shape1);
  }
}

void ContactListener::Persist(b2ContactPoint *point) { }

void ContactListener::Remove(b2ContactPoint *point) { }



Object *createObjectByTypeId(int typeId, World &master) {
  switch(typeId) {
    case T_POLY_SHAPE:
      return new PolyShapeObject(master);
    case T_BODY:
      return new BodyObject(master);
    case T_DRAW_POLY:
      return new DrawPolyObject(master);
    default:
      pmsgErrorF("Attempt to create unknown object (typeId=%d).",int(typeId));
      return 0;
  }
}

bool xconnect(Object *objectA, int socketIdA, Object *objectB) {
  if(objectA->connectToSocket(socketIdA,objectB)) {
    objectB->createRelationTo(objectA,socketIdA);
    return true;
  } else return false;
}

void xdisconnect(Object *objectA, int socketIdA, Object *objectB) {
  objectA->disconnectFromSocket(socketIdA,objectB);
  objectB->destroyRelationTo(objectA,socketIdA);
}

//-----------------------------------------------------------------------------
// Object class - definition
//-----------------------------------------------------------------------------

bool Object::connectToSocket(int id, Object *object) { return true; }

void Object::disconnectFromSocket(int id, Object *object) { }

void Object::createRelationTo(Object *object, int id) {
  relList_.add(ObjectPlusSocketId(object,id));
}

void Object::destroyRelationTo(Object *object, int id) {
      // !! Looking for the right node takes some time...
  relList_.removeElement(ObjectPlusSocketId(object,id));
}

// This is the right place to inform all objects we use that we are going to die.
void Object::beforeDestruction() {
  // Disconnect this object from all sockets it may be connected to.
  for(int i=relList_.size(); i--;) {
    relList_[i].object->disconnectFromSocket(relList_[i].socketId, this);
  }
  flags_|=FLAG_DESTRUCTOR_OK;
}

Object::Object(World &master) : master_(master), flags_(0), next_(0), prev_(0) {
  master_.addObject(this);
};

Object::~Object() {
  master_.removeObject(this);
}

void Object::scheduleForDestruction() {
  if(!(flags_&FLAG_DESTROY)) {
    flags_|=FLAG_DESTROY; flags_&=(~FLAG_VISITED);
  }
}

const char *Object::socketName(int socketId) { return 0; }

int Object::socketCount() { return 0; }

int Object::connectionCount(int socketId) { return 0; }

// Return i-th connected object.
Object* Object::getConnected(int socketId, int i) { return 0; }

Object *Object::nextObject() { return next_; }

Object *Object::prevOBject() { return prev_; }

void Object::store(std::ofstream &stream) { }
void Object::load(std::ifstream &stream) { }

//-----------------------------------------------------------------------------
// World class
//-----------------------------------------------------------------------------
// World is a context in which entities exist.
// - Entities have (indirect) access to other entities.
// - Entities have access to the map.

Object *World::firstObject() { return firstObject_; }

void World::addObject(Object *object) {
  object->prev_=0;
  object->next_=firstObject_;
  if(firstObject_) firstObject_->prev_=object;
  firstObject_=object;
}

void World::removeObject(Object *object) {
  // Check it destructor has been called properly.
  ASSURE(object->flags_&FLAG_DESTRUCTOR_OK);
  // Remove object.
  if(object->prev_) object->prev_->next_=object->next_;
  else firstObject_=object->next_;
  if(object->next_) object->next_->prev_=object->prev_;
}

void World::destroyAll() {
  Object *object=firstObject_; Object *next;
  for(;object;object=next) {
    next=object->nextObject();
    // Destructor will be OK as broken relations may do no harm.
    object->flags_|=FLAG_DESTRUCTOR_OK;
    delete object;
  }
}

void World::destructionPass() {
  // !! Going object by object is not very efficient.
  
  // First tell objects what is going on...
  bool found;
  Object *object = firstObject_;
  do {
    found=false;
    for(;object;object=object->nextObject()) {
      if((object->flags_&FLAG_DESTROY) && !(object->flags_&FLAG_VISITED)) {
        found=true;
        object->beforeDestruction();
        object->flags_|=FLAG_VISITED;
      }
    }
  } while(found);
  
  // Then destroy them.
  object = firstObject(); Object *next;
  for(;object;object=next) {
    next=object->nextObject();
    if(object->flags_&FLAG_DESTROY) delete object;
  }
}

void World::save(const fs::path &path) {
  fs::ofstream file(path,std::ios::binary);
  // Enumerate objects.
  int id=0;
  for(Object *object=firstObject(); object; object=object->nextObject()) {
    object->id_=id++;
    object->flags_|=FLAG_VALID_ID;
  }
  
  // Write number of objects.
  net_write<Uint16>(file,id);
  
  // Store objects.
  for(Object *object=firstObject(); object; object=object->nextObject()) {
    // Store type.
    net_write<Uint16>(file,object->getTypeId());
    // Store object.
    std::streampos p1=file.tellp();
    net_write<Uint16>(file,0);
    object->store(file);
    std::streampos p2=file.tellp();
    file.seekp(p1);
    net_write<Uint16>(file,p2-p1-sizeof(Uint16)); // !! Customize this.
    file.seekp(p2);
  }
  
  // Store relations.
  for(Object *object=firstObject(); object; object=object->nextObject()) {
    net_write<Uint16>(file,object->relList_.size());
    for(int i=object->relList_.size(); i--;) {
      net_write<Uint16>(file,object->relList_[i].object->id_);
      net_write<Uint16>(file,object->relList_[i].socketId);
    }
  }
  
  // Invalidate id's.
  for(Object *object=firstObject(); object; object=object->nextObject()) {
    object->flags_&=(~FLAG_VALID_ID);
  }
}

// And here comes the best part...
void World::load(const fs::path &path) {
  // First step - destroy all existing objects.
  destroyAll();
  // Then open file.
  fs::ifstream file(path,std::ios::binary);
  
  // !! Not very fortunate data structure here.
  RawVector<Object*> objectList;
  
  // Load objects.
  int n=net_read<Uint16>(file);
  for(int i=0; i<n; i++) {
    int typeId=net_read<Uint16>(file);
    Object *object=createObjectByTypeId(typeId,*this);
    // Created objects will be automatically destroyed.
    ASSURE(object!=0);
    objectList.add(object);
    std::streampos p=file.tellg(); p+=net_read<Uint16>(file)+sizeof(Uint16);
    object->load(file);
    file.seekg(p);
  }
  
  std::streampos p=file.tellg();
  
  // Load relations.
  for(int i=0; i<n; i++) {
    int nrel=net_read<Uint16>(file);
    for(int j=0; j<nrel; j++) {
      int id=net_read<Uint16>(file);
      // !! We could continue loading.
      ASSURE(id>=0); ASSURE(id<objectList.size());
      Object *objectA=objectList[id];
      ASSURE(objectA!=0);
      int socketIdA=net_read<Uint16>(file);
      Object *objectB=objectList[i];
      if(!xconnect(objectA,socketIdA,objectB)) {
        pmsgErrorF("World::load(...): xconnect failed.");
      }
    }
  }
}

World::World(Map *map) {
  map_ = map; pause_=false;
  firstObject_=0;
  
  // Create physical world.
  b2AABB worldAABB;
  worldAABB.lowerBound.Set(-8,-8);
  worldAABB.upperBound.Set(8,8);
  b2Vec2 gravity(0.0f,10.0f);
  bool doSleep = true;
  world_ = new b2World(worldAABB, gravity, doSleep);

  // Create and set contact listener.
  contactListener_=new ContactListener(this);
  world_->SetListener(contactListener_);
  
  // Create form list.
  formList_ = new FormList(this);
  
  // Create physical representation of the map.
  b2BodyDef worldBodyDef;
  worldBodyDef.position.Set(0.0f, 0.0f);
  b2Body *worldBody = world_->CreateStaticBody(&worldBodyDef);

  // Add background.
  // !! This should be changed.
  //bgScrapList_.add(BgScrap("data/bg/GimpHorizon.png",0,-512,0,32,1024));
  //bgScrapList_.add(BgScrap("data/bg/Clouds.png", 256, -128, 0.005, 800, 0));
  bgScrapList_.add(BgScrap(X2D_PATH_BACKGROUND "BlueCurtain2.png",-400,-300,0,800,600));
}

World::~World() {
  destroyAll();
  for(unsigned i=0; i<entList_.size(); i++)
    delete entList_[i];
  delete world_;
  delete contactListener_;
}

// void World::save(const fs::path &path) {
//   fs::ofstream file(path, std::ios_base::binary);
//   file.write("X2D/MAP", 8);
//   file.put(5); file.put(0);
//   Uint32 dataOffsetPos = file.tellp();
//   file.seekp(4,std::ios_base::cur);
//   Uint32 dataOffset = file.tellp();
//   
//   for(b2Body *b = world_->GetBodyList(); b; b=b->GetNext()) {
//     // Do not store bodies that have no UserData.
//     if(b->GetUserData()==0) continue;
//     net_write(file, b->m_shapeCount);
//     net_write(file, b->GetPosition().x);
//     net_write(file, b->GetPosition().y);
//     EntityHandler *eh = static_cast<EntityHandler*>(b->GetUserData());
//     Uint32 id = formList_->getIdOfEntityHandler(eh);
//     net_write(file, id);
//     for(b2Shape *s = b->GetShapeList(); s; s=s->GetNext()) {
//       net_write(file, s->m_density);
//       pmsgDebugF("Density: %f\n", s->m_density);
//       net_write(file, s->m_friction);
//       net_write(file, s->m_restitution);
//       net_write(file, static_cast<Sint8>(s->GetType()));
//       pmsgDebugF("Shape type: %d\n", static_cast<int> (s->GetType()));
//       if(s->GetType()==e_circleShape) {
//         b2CircleShape *cs = static_cast<b2CircleShape*>(s);
//         b2Vec2 pos = cs->GetLocalPosition();
//         net_write(file, pos.x);
//         net_write(file, pos.y);
//         net_write(file, cs->GetRadius());
//       } else if(s->GetType()==e_polygonShape) {
//         b2PolygonShape *ps = static_cast<b2PolygonShape*>(s);
//         net_write(file, ps->GetVertexCount());
//         for(int i=0; i<ps->GetVertexCount(); i++) {
//           b2Vec2 vert = ps->m_vertices[i];
//           net_write(file, vert.x);
//           net_write(file, vert.y);
//         }
//       }
//     }
//   }
//   net_write(file,static_cast<Sint32>(0));
//   
//   // !! Write data here.
//   file.seekp(dataOffsetPos,std::ios_base::beg);
//   net_write(file,dataOffset);
// }
// 
// void World::load(const fs::path &path) {
//   // Destroy the world.
//   
//   b2Body *b;
//   while((b = world_->GetBodyList())!=0) {
//     world_->DestroyBody(b);
//   }
//   
//   // Load data.
//   
//   fs::ifstream file(path, std::ios_base::binary);
//   char magic[8];
//   file.read(magic,8);
//   ASSURE(strncmp(magic,"X2D/MAP", 8)==0);
//   Uint8 majorVersionNumber; net_read(file, majorVersionNumber);
//   ASSURE(majorVersionNumber==5);
//   Uint8 minorVersionNumber; net_read(file, minorVersionNumber);
//   ASSURE(minorVersionNumber==0);
//   
//   // Read offsets.
//   Uint32 dataOffset;
//   net_read(file, dataOffset);
//   
//   // Go to the beginning of the data.
//   file.seekg(dataOffset,std::ios_base::beg);
//   
//   while(true) {
//     int32 shapeCount; net_read(file, shapeCount);
//     if(shapeCount==0) break;
//     b2BodyDef bodyDef;
//     
//     net_read(file, bodyDef.position.x);
//     net_read(file, bodyDef.position.y);
//     
//     Uint32 id = net_read<Uint32>(file);
//     EntityHandler *eh = formList_->getEntityHandlerById(id);
//     bodyDef.userData=eh;
//     
//     // !! We should check if this body is a dynamic one.
//     b2Body *body = world_->CreateStaticBody(&bodyDef);
//     
//     for(int i=0; i<shapeCount; i++) {
//     
//       float32 density = net_read<float32>(file);
//       float32 friction = net_read<float32>(file);
//       float32 restitution = net_read<float32>(file);
//       
//       b2ShapeType shapeType = static_cast<b2ShapeType>(net_read<Sint8>(file));
//       if(shapeType==e_circleShape) {
//         b2CircleDef circleDef;
//         circleDef.density=density;
//         circleDef.friction=friction;
//         circleDef.restitution=restitution;
//         net_read(file, circleDef.localPosition.x);
//         net_read(file, circleDef.localPosition.y);
//         net_read(file, circleDef.radius);
//         body->CreateShape(&circleDef);
//       } else if(shapeType==e_polygonShape) {
//         b2PolygonDef polygonDef;
//         polygonDef.density=density;
//         polygonDef.friction=friction;
//         polygonDef.restitution=restitution;
//         net_read(file, polygonDef.vertexCount);
//         for(int j=0; j<polygonDef.vertexCount; j++) {
//           net_read(file, polygonDef.vertices[j].x);
//           net_read(file, polygonDef.vertices[j].y);
//         }
//         body->CreateShape(&polygonDef);
//       }
//       pmsgDebugF("One shape down.\n");
//     }
//     
//     //body->SetMassFromShapes();
//   }
//   
// }

void World::createEntity(unsigned formId, int x, int y) {
  formList_->createForm(this,formId,x,y);
}

void World::add(Entity *ent) {
  entList_.add(ent);
}

void World::remove(Entity *ent) {
  entList_.removeElement(ent);
}

// This is old background drawing code taken from the View class.
void World::drawBackground(int dx, int dy, int w, int h, LookCb fn, void *data) {
  int x=0, y=0, bg_dx=0, bg_dy=0;
  
  bg_dx=-(dx/2)%background_->w;
  if(bg_dx>0) bg_dx=bg_dx-background_->w;

  // More sophisticated method of calculating bg_dy
  // Unknown results if the background is smaller than the screen
  bg_dy=(map_->getH()*32-background_->h)*dy/(map_->getH()*32-h)-dy;

  //bg_dy=-(dy/4)%bg->h;
  if(bg_dy>0) bg_dy=bg_dy-background_->h;
  
  for(y=bg_dy; y<h; y+=background_->h) {
    for(x=bg_dx;x<w; x+=background_->w) {
      fn(x,y,background_, data);
    }
  }
}

const float32 X2D_TIME_STEP = 1/100.0f;
const int32 X2D_ITERS = 10;

void World::timeCb() {
  if(!pause_) {
    world_->Step(X2D_TIME_STEP, X2D_ITERS);
    for(unsigned i=0; i<entList_.size(); i++) {
      entList_[i]->timeCb();
    }
  }
}

//-----------------------------------------------------------------------------
// DrawPolyObject class definition.
//-----------------------------------------------------------------------------

int DrawPolyObject::getTypeId() { return T_DRAW_POLY; }

void DrawPolyObject::draw(GraphicContext &g, b2PolygonShape *poly, float32 x, float32 y) {
  g.startOpenGL();
  glDisable(GL_TEXTURE_2D);

  glColor4f(0.0f,0.0f,0.0f,1.0f);

  glTranslatef(x,y,0);
  glRotatef(static_cast<GLfloat>(poly->GetBody()->GetAngle()*180/UGLY_PI),0,0,1);
  
  unsigned n = poly->GetVertexCount();
  glBegin(GL_POLYGON);
  for(unsigned i=0; i<n; i++) {
    const b2Vec2 &xy = poly->GetVertices()[i];
    glVertex2f(xy.x*64, xy.y*64);
  }
  glEnd();

  g.endOpenGL();
}

//-----------------------------------------------------------------------------
// BodyObject class definition.
//-----------------------------------------------------------------------------

int BodyObject::getTypeId() { return T_BODY; }

b2Body *BodyObject::getBody() {
  return body_;
}

BodyObject::BodyObject(World &master) : Object(master) {
      // We don't have position to create body.
  body_=0;
}

BodyObject::BodyObject(World &master, b2BodyDef *bodyDef, bool isStatic) : Object(master) {
  ASSURE(bodyDef!=0);
  bodyDef->userData=this;
  if(isStatic) {
    body_=master.getB2World()->CreateStaticBody(bodyDef);
  } else {
    body_=master.getB2World()->CreateDynamicBody(bodyDef);
  }
}

//-----------------------------------------------------------------------------
// PolyShapeObject class definition.
//-----------------------------------------------------------------------------

PolyShapeObject::PolyShapeObject(World &master) : Object(master) {
  // Shape cannot exist without body, so we create shape definition instead.
  polyDef_=new b2PolygonDef;
  polyDef_->userData=this;
  polyShape_=0;
  bodyObject_=0;
  drawPolyObject_=0;
}

PolyShapeObject::PolyShapeObject(World &master, const b2PolygonDef &polyDef) : Object(master) {
  polyDef_=new b2PolygonDef;
  *polyDef_=polyDef;
  polyDef_->userData=this;
  polyShape_=0;
  bodyObject_=0;
  drawPolyObject_=0;
}

PolyShapeObject::~PolyShapeObject() {
  delete polyDef_;
  delete polyShape_;
}

int PolyShapeObject::getTypeId() {
  return T_POLY_SHAPE;
}

bool PolyShapeObject::connectToSocket(int socketId, Object *object) {
  switch(socketId) {
    case 0:
      if(!bodyObject_) {
        bodyObject_=dynamic_cast<BodyObject*>(object);
            // We can connect only body object already having a body.
        if(bodyObject_->getBody()) {
              // Create real shape.
          polyShape_=static_cast<b2PolygonShape*>(bodyObject_->getBody()->CreateShape(polyDef_));
          if(polyShape_) {
            delete polyDef_;
            polyDef_=0;
            return true;
          } else return false;
        } else return false;
      } else return false;
    case 1:
      if(!drawPolyObject_) {
        drawPolyObject_=dynamic_cast<DrawPolyObject*>(object);
        return drawPolyObject_!=0;
      } else return false;
    case 2:
    default:
      return 0;
  }
}

void PolyShapeObject::disconnectFromSocket(int socketId, Object *object) {
  switch(socketId) {
    case 0:
          // !! What happens when body is destroyed? Does it destroy shapes
          // first?
      if(bodyObject_==object) {
        ASSURE(object!=0); ASSURE(polyDef_==0); ASSURE(polyShape_!=0);
            
            // Create polygon definition and copy data.
        polyDef_=new b2PolygonDef();
        polyDef_->vertexCount=polyShape_->m_vertexCount;
        for(int i=0; i<polyShape_->m_vertexCount; i++) {
          polyDef_->vertices[i]=polyShape_->m_vertices[i];
        }
        polyDef_->friction=polyShape_->m_friction;
        polyDef_->restitution=polyShape_->m_restitution;
        polyDef_->density=polyShape_->m_density;
        polyDef_->categoryBits=polyShape_->m_categoryBits;
        polyDef_->maskBits=polyShape_->m_maskBits;
        polyDef_->groupIndex=polyShape_->m_groupIndex;
        polyDef_->isSensor=polyShape_->m_isSensor;
        polyDef_->userData=this;
            
            // Destroy current shape and remove itself from the body.
        bodyObject_->getBody()->DestroyShape(polyShape_);
        polyShape_=0;
            
            // And finally...
        bodyObject_=0;
      }
      break;
  }
}

void PolyShapeObject::beforeDestruction() {
  if(bodyObject_) xdisconnect(this,0,bodyObject_);
  if(drawPolyObject_) xdisconnect(this,1,drawPolyObject_);
}

const char *PolyShapeObject::socketName(int socketId) {
  switch(socketId) {
    case 0: return "BodyObject";
    case 1: return "DrawPolyObject";
    case 2: return "CollHandlerObject";
    default: return 0;
  }
}

int PolyShapeObject::socketCount() { return 3; }

int PolyShapeObject::connectionCount(int socketId) {
  switch(socketId) {
    case 0: return bodyObject_!=0;
    case 1: return drawPolyObject_!=0;
    default: return 0;
  }
}

Object* PolyShapeObject::getConnected(int socketId, int i) {
  switch(socketId) {
    case 0:
      if(i==0) return bodyObject_;
      else return 0;
    case 1:
      if(i==0) return drawPolyObject_;
      else return 0;
    default:
      return 0;
  }
}

void PolyShapeObject::draw(GraphicContext &g, float32 x, float32 y) {
  if(drawPolyObject_ && polyShape_) drawPolyObject_->draw(g,polyShape_,x,y);
}

} //namespace x2d
